package game.objects;
import game.Game;
import game.engine.BodyToGraphics;
import game.engine.Empty;
import game.engine.GSync;
import game.engine.GSync.SyncType;
import graphics.objects.GraphicsObject;
import graphics.system.Color;

import java.io.Serializable;
import java.util.Vector;

import physics.Body;
import physics.collision.RayCastInfo;
import physics.collision.Raycaster;
import physics.joints.Joint;
import physics.joints.TwoBodyJoint;
import tools.Help;
import tools.Point;
import tools.id.IdObject;
/**
 * A game object is an abstract representation of anything that you could possibly understand under "game object". It consists of a number of physical bodies and joints (constraints that lie upon a body like "the distance between this body and that body should be 20m") and a graphical representation, a number of graphicsObjects that could either just be the bodies or the joints but can also be static things in the world (the sun)
 * 
 * This class provides methods for adding and removing bodies, joints and graphicsObjects. Note that it is strongly recommended not to call any of those methods that have the additional fromSystem parameter to them. This parameter is set solely by the engine and should not be accessed whenever you write game code. It is needed to make sure that clients in the network do not add their own game objects to themselves, but instead only those that they receive from the network.
 * 
 * @author toni
 */
public abstract class GameObject implements Serializable, IdObject {
	protected transient Game game;
	public int id = IdObject.DEFAULTID;
	private Vector<Integer> bodies;
	private Vector<Integer> joints;
	private Vector<Integer> graphicsObjects;
	private Vector<GSync> graphicsSync;
	private NameSystem<Body> bodyNames;
	private NameSystem<Joint> jointNames;
	private NameSystem<GraphicsObject> groNames;
	public int numBodies, numJoints, numGraphicsObjects;
	private float creationTime;
	public GameObject(Game g, Point pos) {
		this(g);
		game.addInitializeInfo(pos);
	}
	public GameObject(Game g, Point pos, float angle) {
		this(g);
		game.addInitializeInfo(pos, angle);
	}
	public GameObject(Game g, Point pos, float angle, float scale) {
		this(g);
		game.addInitializeInfo(pos, angle, scale);
	}
	public GameObject(Game g) {
		game = g;
		bodies = new Vector<Integer>();
		joints = new Vector<Integer>();
		graphicsObjects = new Vector<Integer>();
		graphicsSync = new Vector<GSync>();
		numBodies = 0;
		numJoints = 0;
		numGraphicsObjects = 0;
		creationTime = game.time;
		bodyNames = new NameSystem<Body>();
		jointNames = new NameSystem<Joint>();
		groNames = new NameSystem<GraphicsObject>();
	}
	public abstract void handle();
	public void setGame(Game g) {
		game = g;
	}
	public float timeExisting() {
		return game.time - creationTime;
	}
	public void initialize(Point pos) {
	}
	public void initialize(Point pos, float angle) {
	}
	public void initialize(Point pos, float angle, float scale) {
	}
	public final void addBody(Body b) {
		if (!game.addBody(b, this))
			return;
		numBodies++;
		bodies.addElement(b.id);
	}
	public final void remBody(Body b) {
		for (int n = 0; n < numJoints; n++) {
			Joint j = getJoint(n);
			if (j.b1Id == b.id) {
				remJoint(j);
				n--;
			}
			else if (j instanceof TwoBodyJoint) {
				if (((TwoBodyJoint) j).b2Id == b.id) {
					remJoint(j);
					n--;
				}
			}
		}
		if (!game.remBody(b, this))
			return;
		numBodies--;
		bodies.removeElement(b.id);
	}
	public final void addJoint(Joint j) {
		if (!game.addJoint(j, this))
			return;
		numJoints++;
		joints.addElement(j.id);
	}
	public final void remJoint(Joint j) {
		if (!game.server)
			return;
		if (!game.remJoint(j, this))
			return;
		numJoints--;
		joints.removeElement(j.id);
	}
	public final void addGraphicsObject(GraphicsObject o, GSync s) {
		if (!game.addGraphicsObject(o, s, this))
			return;
		numGraphicsObjects++;
		graphicsObjects.addElement(o.id);
		graphicsSync.add(s);
	}
	public final void remGraphicsObject(GraphicsObject o) {
		if (!game.remGraphicsObject(o, this))
			return;
		numGraphicsObjects--;
		int index = graphicsObjects.indexOf(o.id);
		graphicsObjects.remove(index);
		graphicsSync.remove(index);
	}
	public void addBodyWithGraphics(Body b, Color c) {
		addBody(b);
		addGraphicsObject(BodyToGraphics.get(b, c), new GSync(b));
	}
	protected void addBodyWithGraphics(Body b, GraphicsObject gro) {
		addBody(b);
		addGraphicsObject(gro, new GSync(b));
	}
	public void remBodyWithGraphics(Body b) {
		Vector<GraphicsObject> toRemove = new Vector<GraphicsObject>();
		for (int i = 0; i < numGraphicsObjects; i++) {
			GraphicsObject gro = getGraphicsObject(i);
			GSync s = getSync(gro);
			if (s.type == SyncType.BODY && s.targetId == b.id) {
				toRemove.add(gro);
			}
		}
		for (GraphicsObject gro : toRemove) {
			remGraphicsObject(gro);
		}
		remBody(b);
	}
	public Body getBody(int i) {
		return game.engine.world.getBodyById(bodies.get(i));
	}
	public Joint getJoint(int i) {
		return game.engine.world.getJointById(joints.get(i));
	}
	public GraphicsObject getGraphicsObject(int i) {
		return game.engine.graphics.getGraphicsObjectById(graphicsObjects.get(i));
	}
	public GSync getSync(int j) {
		return graphicsSync.get(j);
	}
	public void setSync(GraphicsObject o, GSync sync) {
		graphicsSync.set(graphicsObjects.indexOf(o.id), sync);
	}
	public GSync getSync(GraphicsObject o) {
		return getSync(graphicsObjects.indexOf(o.id));
	}
	public boolean containsBody(Body b) {
		return bodies.contains(b.id);
	}
	public boolean containsJoint(Joint j) {
		return joints.contains(j.id);
	}
	public boolean containsGraphicsObject(GraphicsObject gr) {
		return graphicsObjects.contains(gr.id);
	}
	// Network methods. DO NOT CALL except from ClientHandler
	public final void addNetworkBody(Body b) {
		numBodies++;
		bodies.addElement(b.id);
	}
	public final void remNetworkBody(Body b) {
		numBodies--;
		bodies.removeElement(b.id);
	}
	public final void addNetworkJoint(Joint j) {
		numJoints++;
		joints.addElement(j.id);
	}
	public final void remNetworkJoint(Joint j) {
		numJoints--;
		joints.removeElement(j.id);
	}
	public final void addNetworkGraphicsObject(GraphicsObject o, GSync s) {
		numGraphicsObjects++;
		graphicsObjects.addElement(o.id);
		graphicsSync.add(s);
	}
	public final void remNetworkGraphicsObject(GraphicsObject o) {
		numGraphicsObjects--;
		int index = graphicsObjects.indexOf(o.id);
		graphicsObjects.remove(index);
		graphicsSync.remove(index);
	}
	@Override
	public String toString() {
		return this.getClass().getCanonicalName() + " id " + id;
	}
	@Override
	public int getId() {
		return id;
	}
	@Override
	public void setId(int id) {
		this.id = id;
	}
	@Override
	public void setIndexInList(int i) {
	}
	public Body getBodyByName(String name) {
		int id = bodyNames.getObjectId(name);
		/*
		 * if(id > this.numJoints){ Help.p("Not such Body: '"+name+"' in "+this.getClass().getSimpleName()+"!("+id+")"); return null; }
		 */
		// return getBody(id);
		return game.engine.world.getBodyById(id);
	}
	public Joint getJointByName(String name) {
		int id = jointNames.getObjectId(name);
		/*
		 * if(id > this.numJoints){ Help.p("Not such Joint: '"+name+"' in "+this.getClass().getSimpleName()+"!("+id+")"); return null; }
		 */
		// return getJoint(id);
		return game.engine.world.getJointById(id);
	}
	public GraphicsObject getGraphicsObjectByName(String name) {
		int id = groNames.getObjectId(name);
		/*
		 * if(id > this.numGraphicsObjects){ Help.p("Not such GraphicsObject: '"+name+"' in "+this.getClass().getSimpleName()+"! ("+id+")"); return null; }
		 */
		// return this.getGraphicsObject(id);
		return game.engine.graphics.getGraphicsObjectById(id);
	}
	public Empty getEmptyByName(String name) {
		int id = groNames.getObjectId(name);
		return (Empty) game.engine.graphics.getGraphicsObjectById(id);
	}
	public void setName(GraphicsObject o, String name) {
		groNames.setName(o, name);
	}
	public void setName(Body o, String name) {
		bodyNames.setName(o, name);
	}
	public void setName(Joint o, String name) {
		jointNames.setName(o, name);
	}
	public String getName(Joint j) {
		String n = jointNames.getName(j);
		n = n != null ? n : "";
		return jointNames.getName(j);
	}
	public String getName(GraphicsObject j) {
		String n = groNames.getName(j);
		n = n != null ? n : "";
		return n;
	}
	public String getName(Body j) {
		String n = bodyNames.getName(j);
		n = n != null ? n : "";
		return n;
	}
	public RayCastInfo castRay(Point p, Point dir, float offset) {
		RayCastInfo ri = Raycaster.getFirstIntersecting(game.engine.world, p, dir, offset);
		return ri;
	}
}
